use super::{bits, canvas, cast, render, types, ec};
use cast::{As, CheckedIntCast};
use render::{Pixel, Renderer};
use std::ops::Index;
use types::{Color, EcLevel, Result, Version};

#[derive(Debug, Clone)]
pub struct QrCode {
  content: Vec<Color>,
  version: Version,
  ec_level: EcLevel,
  width: usize,
}

impl QrCode {
  pub fn new<D: AsRef<[u8]>>(data: D) -> Result<Self> {
    Self::with_error_correction_level(data, EcLevel::M)
  }

  pub fn with_error_correction_level<D: AsRef<[u8]>>(
    data: D,
    ec_level: EcLevel,
  ) -> Result<Self> {
    let bits = bits::encode_auto(data.as_ref(), ec_level)?;
    Self::with_bits(bits, ec_level)
  }

  pub fn with_version<D: AsRef<[u8]>>(
    data: D,
    version: Version,
    ec_level: EcLevel,
  ) -> Result<Self> {
    let mut bits = bits::Bits::new(version);
    bits.push_optimal_data(data.as_ref())?;
    bits.push_terminator(ec_level)?;
    Self::with_bits(bits, ec_level)
  }

  pub fn with_bits(bits: bits::Bits, ec_level: EcLevel) -> Result<Self> {
    let version = bits.version();
    let data = bits.into_bytes();
    let (encoded_data, ec_data) =
      ec::construct_codewords(&*data, version, ec_level)?;
    let mut canvas = canvas::Canvas::new(version, ec_level);
    canvas.draw_all_functional_patterns();
    canvas.draw_data(&*encoded_data, &*ec_data);
    let canvas = canvas.apply_best_mask();
    Ok(Self {
      content: canvas.into_colors(),
      version,
      ec_level,
      width: version.width().as_usize(),
    })
  }

  pub fn version(&self) -> Version {
    self.version
  }

  pub fn error_correction_level(&self) -> EcLevel {
    self.ec_level
  }

  pub fn width(&self) -> usize {
    self.width
  }

  pub fn max_allowed_errors(&self) -> usize {
    ec::max_allowed_errors(self.version, self.ec_level)
      .expect("invalid version or ec_level")
  }

  pub fn is_functional(&self, x: usize, y: usize) -> bool {
    let x = x
      .as_i16_checked()
      .expect("coordinate is to large for QR code");
    let y = y
      .as_i16_checked()
      .expect("coordinate is to large for QR code");
    canvas::is_functional(self.version, self.version.width(), x, y)
  }

  pub fn to_colors(&self) -> Vec<Color> {
    self.content.clone()
  }

  pub fn into_colors(self) -> Vec<Color> {
    self.content
  }

  pub fn render<P: Pixel>(&self) -> Renderer<P> {
    let quiet_zone = if self.version.is_micro() { 2 } else { 4 };
    Renderer::new(&self.content, self.width, quiet_zone)
  }
}

impl Index<(usize, usize)> for QrCode {
  type Output = Color;
  fn index(&self, (x, y): (usize, usize)) -> &Self::Output {
    let index = y * self.width + x;
    &self.content[index]
  }
}
